/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 *
 * @format
 */

'use strict';

function _slicedToArray(arr, i) {
  return (
    _arrayWithHoles(arr) ||
    _iterableToArrayLimit(arr, i) ||
    _unsupportedIterableToArray(arr, i) ||
    _nonIterableRest()
  );
}
function _nonIterableRest() {
  throw new TypeError(
    'Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.',
  );
}
function _unsupportedIterableToArray(o, minLen) {
  if (!o) return;
  if (typeof o === 'string') return _arrayLikeToArray(o, minLen);
  var n = Object.prototype.toString.call(o).slice(8, -1);
  if (n === 'Object' && o.constructor) n = o.constructor.name;
  if (n === 'Map' || n === 'Set') return Array.from(o);
  if (n === 'Arguments' || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
    return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
  if (len == null || len > arr.length) len = arr.length;
  for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
  return arr2;
}
function _iterableToArrayLimit(arr, i) {
  var _i =
    null == arr
      ? null
      : ('undefined' != typeof Symbol && arr[Symbol.iterator]) ||
        arr['@@iterator'];
  if (null != _i) {
    var _s,
      _e,
      _x,
      _r,
      _arr = [],
      _n = !0,
      _d = !1;
    try {
      if (((_x = (_i = _i.call(arr)).next), 0 === i)) {
        if (Object(_i) !== _i) return;
        _n = !1;
      } else
        for (
          ;
          !(_n = (_s = _x.call(_i)).done) &&
          (_arr.push(_s.value), _arr.length !== i);
          _n = !0
        );
    } catch (err) {
      (_d = !0), (_e = err);
    } finally {
      try {
        if (!_n && null != _i.return && ((_r = _i.return()), Object(_r) !== _r))
          return;
      } finally {
        if (_d) throw _e;
      }
    }
    return _arr;
  }
}
function _arrayWithHoles(arr) {
  if (Array.isArray(arr)) return arr;
}
const _require = require('./Utils'),
  createAliasResolver = _require.createAliasResolver,
  getModules = _require.getModules;
const _require2 = require('../../parsers/parsers-commons'),
  unwrapNullable = _require2.unwrapNullable;
const HostFunctionTemplate = ({
  hasteModuleName,
  methodName,
  returnTypeAnnotation,
  args,
}) => {
  const isNullable = returnTypeAnnotation.type === 'NullableTypeAnnotation';
  const isVoid = returnTypeAnnotation.type === 'VoidTypeAnnotation';
  const methodCallArgs = ['    rt', ...args].join(',\n    ');
  const methodCall = `static_cast<${hasteModuleName}CxxSpecJSI *>(&turboModule)->${methodName}(\n${methodCallArgs}\n  )`;
  return `static jsi::Value __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}(jsi::Runtime &rt, TurboModule &turboModule, const jsi::Value* args, size_t count) {${
    isVoid
      ? `\n  ${methodCall};`
      : isNullable
      ? `\n  auto result = ${methodCall};`
      : ''
  }
  return ${
    isVoid
      ? 'jsi::Value::undefined()'
      : isNullable
      ? 'result ? jsi::Value(std::move(*result)) : jsi::Value::null()'
      : methodCall
  };
}`;
};
const ModuleTemplate = ({
  hasteModuleName,
  hostFunctions,
  moduleName,
  methods,
}) => {
  return `${hostFunctions.join('\n')}

${hasteModuleName}CxxSpecJSI::${hasteModuleName}CxxSpecJSI(std::shared_ptr<CallInvoker> jsInvoker)
  : TurboModule("${moduleName}", jsInvoker) {
${methods
  .map(({methodName, paramCount}) => {
    return `  methodMap_["${methodName}"] = MethodMetadata {${paramCount}, __hostFunction_${hasteModuleName}CxxSpecJSI_${methodName}};`;
  })
  .join('\n')}
}`;
};
const FileTemplate = ({libraryName, modules}) => {
  return `/**
 * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
 *
 * Do not edit this file as changes may cause incorrect behavior and will be lost
 * once the code is regenerated.
 *
 * ${'@'}generated by codegen project: GenerateModuleCpp.js
 */

#include "${libraryName}JSI.h"

namespace facebook {
namespace react {

${modules}


} // namespace react
} // namespace facebook
`;
};
function serializeArg(moduleName, arg, index, resolveAlias, enumMap) {
  const nullableTypeAnnotation = arg.typeAnnotation,
    optional = arg.optional;
  const _unwrapNullable = unwrapNullable(nullableTypeAnnotation),
    _unwrapNullable2 = _slicedToArray(_unwrapNullable, 2),
    typeAnnotation = _unwrapNullable2[0],
    nullable = _unwrapNullable2[1];
  const isRequired = !optional && !nullable;
  let realTypeAnnotation = typeAnnotation;
  if (realTypeAnnotation.type === 'TypeAliasTypeAnnotation') {
    realTypeAnnotation = resolveAlias(realTypeAnnotation.name);
  }
  function wrap(callback) {
    const val = `args[${index}]`;
    const expression = callback(val);
    if (isRequired) {
      return expression;
    } else {
      let condition = `${val}.isNull() || ${val}.isUndefined()`;
      if (optional) {
        condition = `count <= ${index} || ${condition}`;
      }
      return `${condition} ? std::nullopt : std::make_optional(${expression})`;
    }
  }
  switch (realTypeAnnotation.type) {
    case 'ReservedTypeAnnotation':
      switch (realTypeAnnotation.name) {
        case 'RootTag':
          return wrap(val => `${val}.getNumber()`);
        default:
          realTypeAnnotation.name;
          throw new Error(
            `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.name}"`,
          );
      }
    case 'StringTypeAnnotation':
      return wrap(val => `${val}.asString(rt)`);
    case 'BooleanTypeAnnotation':
      return wrap(val => `${val}.asBool()`);
    case 'EnumDeclaration':
      switch (realTypeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return wrap(val => `${val}.asNumber()`);
        case 'StringTypeAnnotation':
          return wrap(val => `${val}.asString(rt)`);
        default:
          throw new Error(
            `Unknown enum type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
          );
      }
    case 'NumberTypeAnnotation':
      return wrap(val => `${val}.asNumber()`);
    case 'FloatTypeAnnotation':
      return wrap(val => `${val}.asNumber()`);
    case 'DoubleTypeAnnotation':
      return wrap(val => `${val}.asNumber()`);
    case 'Int32TypeAnnotation':
      return wrap(val => `${val}.asNumber()`);
    case 'ArrayTypeAnnotation':
      return wrap(val => `${val}.asObject(rt).asArray(rt)`);
    case 'FunctionTypeAnnotation':
      return wrap(val => `${val}.asObject(rt).asFunction(rt)`);
    case 'GenericObjectTypeAnnotation':
      return wrap(val => `${val}.asObject(rt)`);
    case 'UnionTypeAnnotation':
      switch (typeAnnotation.memberType) {
        case 'NumberTypeAnnotation':
          return wrap(val => `${val}.asNumber()`);
        case 'ObjectTypeAnnotation':
          return wrap(val => `${val}.asObject(rt)`);
        case 'StringTypeAnnotation':
          return wrap(val => `${val}.asString(rt)`);
        default:
          throw new Error(
            `Unsupported union member type for param  "${arg.name}, found: ${realTypeAnnotation.memberType}"`,
          );
      }
    case 'ObjectTypeAnnotation':
      return wrap(val => `${val}.asObject(rt)`);
    case 'MixedTypeAnnotation':
      return wrap(val => `jsi::Value(rt, ${val})`);
    default:
      realTypeAnnotation.type;
      throw new Error(
        `Unknown prop type for "${arg.name}, found: ${realTypeAnnotation.type}"`,
      );
  }
}
function serializePropertyIntoHostFunction(
  moduleName,
  hasteModuleName,
  property,
  resolveAlias,
  enumMap,
) {
  const _unwrapNullable3 = unwrapNullable(property.typeAnnotation),
    _unwrapNullable4 = _slicedToArray(_unwrapNullable3, 1),
    propertyTypeAnnotation = _unwrapNullable4[0];
  return HostFunctionTemplate({
    hasteModuleName,
    methodName: property.name,
    returnTypeAnnotation: propertyTypeAnnotation.returnTypeAnnotation,
    args: propertyTypeAnnotation.params.map((p, i) =>
      serializeArg(moduleName, p, i, resolveAlias, enumMap),
    ),
  });
}
module.exports = {
  generate(libraryName, schema, packageName, assumeNonnull = false) {
    const nativeModules = getModules(schema);
    const modules = Object.keys(nativeModules)
      .map(hasteModuleName => {
        const nativeModule = nativeModules[hasteModuleName];
        const aliasMap = nativeModule.aliasMap,
          enumMap = nativeModule.enumMap,
          properties = nativeModule.spec.properties,
          moduleName = nativeModule.moduleName;
        const resolveAlias = createAliasResolver(aliasMap);
        const hostFunctions = properties.map(property =>
          serializePropertyIntoHostFunction(
            moduleName,
            hasteModuleName,
            property,
            resolveAlias,
            enumMap,
          ),
        );
        return ModuleTemplate({
          hasteModuleName,
          hostFunctions,
          moduleName,
          methods: properties.map(
            ({name: propertyName, typeAnnotation: nullableTypeAnnotation}) => {
              const _unwrapNullable5 = unwrapNullable(nullableTypeAnnotation),
                _unwrapNullable6 = _slicedToArray(_unwrapNullable5, 1),
                params = _unwrapNullable6[0].params;
              return {
                methodName: propertyName,
                paramCount: params.length,
              };
            },
          ),
        });
      })
      .join('\n');
    const fileName = `${libraryName}JSI-generated.cpp`;
    const replacedTemplate = FileTemplate({
      modules,
      libraryName,
    });
    return new Map([[fileName, replacedTemplate]]);
  },
};
